 /*******************************************************************************
  * Copyright (c) 2003, 2007 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/
 package org.eclipse.osgi.internal.resolver;

 import java.util.*;

 import org.eclipse.osgi.framework.debug.Debug;
 import org.eclipse.osgi.framework.debug.FrameworkDebugOptions;
 import org.eclipse.osgi.framework.internal.core.Constants;
 import org.eclipse.osgi.framework.internal.core.FilterImpl;
 import org.eclipse.osgi.framework.util.*;
 import org.eclipse.osgi.internal.baseadaptor.StateManager;
 import org.eclipse.osgi.service.resolver.*;
 import org.eclipse.osgi.util.ManifestElement;
 import org.osgi.framework.*;

 public abstract class StateImpl implements State {
     public static final String [] PROPS = {"osgi.os", "osgi.ws", "osgi.nl", "osgi.arch", Constants.OSGI_FRAMEWORK_SYSTEM_PACKAGES, Constants.OSGI_RESOLVER_MODE, Constants.FRAMEWORK_EXECUTIONENVIRONMENT, "osgi.resolveOptional", "osgi.genericAliases"}; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$
 transient private Resolver resolver;
     transient private StateDeltaImpl changes;
     transient private boolean resolving = false;
     transient private HashSet removalPendings = new HashSet();
     private boolean resolved = true;
     private long timeStamp = System.currentTimeMillis();
     private KeyedHashSet bundleDescriptions = new KeyedHashSet(false);
     private HashMap resolverErrors = new HashMap();
     private StateObjectFactory factory;
     private KeyedHashSet resolvedBundles = new KeyedHashSet();
     boolean fullyLoaded = false;
     private boolean dynamicCacheChanged = false;
     // only used for lazy loading of BundleDescriptions
 private StateReader reader;
     private Dictionary[] platformProperties = {new Hashtable(PROPS.length)}; // Dictionary here because of Filter API
 private long highestBundleId = -1;
     private HashSet platformPropertyKeys = new HashSet(PROPS.length);

     private static long cumulativeTime;

     // to prevent extra-package instantiation
 protected StateImpl() {
         // always add the default platform property keys.
 addPlatformPropertyKeys(PROPS);
     }

     public boolean addBundle(BundleDescription description) {
         if (!basicAddBundle(description))
             return false;
         String platformFilter = description.getPlatformFilter();
         if (platformFilter != null) {
             try {
                 // add any new platform filter propery keys this bundle is using
 FilterImpl filter = (FilterImpl) FrameworkUtil.createFilter(platformFilter);
                 addPlatformPropertyKeys(filter.getAttributes());
             } catch (InvalidSyntaxException e) {
                 // ignore this is handled in another place
 }
         }
         resolved = false;
         getDelta().recordBundleAdded((BundleDescriptionImpl) description);
         if (Constants.getInternalSymbolicName().equals(description.getSymbolicName()))
             resetSystemExports();
         if (resolver != null)
             resolver.bundleAdded(description);
         updateTimeStamp();
         return true;
     }

     public boolean updateBundle(BundleDescription newDescription) {
         BundleDescriptionImpl existing = (BundleDescriptionImpl) bundleDescriptions.get((BundleDescriptionImpl) newDescription);
         if (existing == null)
             return false;
         if (!bundleDescriptions.remove(existing))
             return false;
         resolvedBundles.remove(existing);
         existing.setStateBit(BundleDescriptionImpl.REMOVAL_PENDING, true);
         if (!basicAddBundle(newDescription))
             return false;
         resolved = false;
         getDelta().recordBundleUpdated((BundleDescriptionImpl) newDescription);
         if (Constants.getInternalSymbolicName().equals(newDescription.getSymbolicName()))
             resetSystemExports();
         if (resolver != null) {
             boolean pending = existing.getDependents().length > 0;
             resolver.bundleUpdated(newDescription, existing, pending);
             if (pending) {
                 getDelta().recordBundleRemovalPending(existing);
                 removalPendings.add(existing);
             } else {
                 // an existing bundle has been updated with no dependents it can safely be unresolved now
 synchronized (this) {
                     try {
                         resolving = true;
                         resolverErrors.remove(existing);
                         resolveBundle(existing, false, null, null, null, null);
                     } finally {
                         resolving = false;
                     }
                 }
             }
         }
         updateTimeStamp();
         return true;
     }

     public BundleDescription removeBundle(long bundleId) {
         BundleDescription toRemove = getBundle(bundleId);
         if (toRemove == null || !removeBundle(toRemove))
             return null;
         return toRemove;
     }

     public boolean removeBundle(BundleDescription toRemove) {
         if (!bundleDescriptions.remove((KeyedElement) toRemove))
             return false;
         resolvedBundles.remove((KeyedElement) toRemove);
         resolved = false;
         getDelta().recordBundleRemoved((BundleDescriptionImpl) toRemove);
         ((BundleDescriptionImpl) toRemove).setStateBit(BundleDescriptionImpl.REMOVAL_PENDING, true);
         if (resolver != null) {
             boolean pending = toRemove.getDependents().length > 0;
             resolver.bundleRemoved(toRemove, pending);
             if (pending) {
                 getDelta().recordBundleRemovalPending((BundleDescriptionImpl) toRemove);
                 removalPendings.add(toRemove);
             } else {
                 // a bundle has been removed with no dependents it can safely be unresolved now
 synchronized (this) {
                     try {
                         resolving = true;
                         resolverErrors.remove(toRemove);
                         resolveBundle(toRemove, false, null, null, null, null);
                     } finally {
                         resolving = false;
                     }
                 }
             }
         }
         updateTimeStamp();
         return true;
     }

     public StateDelta getChanges() {
         return getDelta();
     }

     private StateDeltaImpl getDelta() {
         if (changes == null)
             changes = new StateDeltaImpl(this);
         return changes;
     }

     public BundleDescription[] getBundles(String symbolicName) {
         if (Constants.OSGI_SYSTEM_BUNDLE.equals(symbolicName))
             symbolicName = Constants.getInternalSymbolicName();
         final List bundles = new ArrayList();
         for (Iterator iter = bundleDescriptions.iterator(); iter.hasNext();) {
             BundleDescription bundle = (BundleDescription) iter.next();
             if (symbolicName.equals(bundle.getSymbolicName()))
                 bundles.add(bundle);
         }
         return (BundleDescription[]) bundles.toArray(new BundleDescription[bundles.size()]);
     }

     public BundleDescription[] getBundles() {
         return (BundleDescription[]) bundleDescriptions.elements(new BundleDescription[bundleDescriptions.size()]);
     }

     public BundleDescription getBundle(long id) {
         BundleDescription result = (BundleDescription) bundleDescriptions.getByKey(new Long (id));
         if (result != null)
             return result;
         // need to look in removal pending bundles;
 for (Iterator iter = removalPendings.iterator(); iter.hasNext();) {
             BundleDescription removedBundle = (BundleDescription) iter.next();
             if (removedBundle.getBundleId() == id) // just return the first matching id
 return removedBundle;
         }
         return null;
     }

     public BundleDescription getBundle(String name, Version version) {
         BundleDescription[] allBundles = getBundles(name);
         if (allBundles.length == 1)
             return version == null || allBundles[0].getVersion().equals(version) ? allBundles[0] : null;

         if (allBundles.length == 0)
             return null;

         BundleDescription unresolvedFound = null;
         BundleDescription resolvedFound = null;
         for (int i = 0; i < allBundles.length; i++) {
             BundleDescription current = allBundles[i];
             BundleDescription base;

             if (current.isResolved())
                 base = resolvedFound;
             else
                 base = unresolvedFound;

             if (version == null || current.getVersion().equals(version)) {
                 if (base != null && (base.getVersion().compareTo(current.getVersion()) <= 0 || base.getBundleId() > current.getBundleId())) {
                     if (base == resolvedFound)
                         resolvedFound = current;
                     else
                         unresolvedFound = current;
                 } else {
                     if (current.isResolved())
                         resolvedFound = current;
                     else
                         unresolvedFound = current;
                 }

             }
         }
         if (resolvedFound != null)
             return resolvedFound;

         return unresolvedFound;
     }

     public long getTimeStamp() {
         return timeStamp;
     }

     public boolean isResolved() {
         return resolved || isEmpty();
     }

     public void resolveConstraint(VersionConstraint constraint, BaseDescription supplier) {
         ((VersionConstraintImpl) constraint).setSupplier(supplier);
     }

     public synchronized void resolveBundle(BundleDescription bundle, boolean status, BundleDescription[] hosts, ExportPackageDescription[] selectedExports, BundleDescription[] resolvedRequires, ExportPackageDescription[] resolvedImports) {
         if (!resolving)
             throw new IllegalStateException (); // TODO need error message here!
 BundleDescriptionImpl modifiable = (BundleDescriptionImpl) bundle;
         // must record the change before setting the resolve state to
 // accurately record if a change has happened.
 getDelta().recordBundleResolved(modifiable, status);
         // force the new resolution data to stay in memory; we will not read this from disk anymore
 modifiable.setLazyLoaded(false);
         modifiable.setStateBit(BundleDescriptionImpl.RESOLVED, status);
         if (status) {
             resolverErrors.remove(modifiable);
             resolvedBundles.add(modifiable);
         } else {
             // remove the bundle from the resolved pool
 resolvedBundles.remove(modifiable);
             modifiable.removeDependencies();
         }
         // to support develoment mode we will resolveConstraints even if the resolve status == false
 // we only do this if the resolved constraints are not null
 if (selectedExports == null || resolvedRequires == null || resolvedImports == null)
             unresolveConstraints(modifiable);
         else
             resolveConstraints(modifiable, hosts, selectedExports, resolvedRequires, resolvedImports);
     }

     public synchronized void removeBundleComplete(BundleDescription bundle) {
         if (!resolving)
             throw new IllegalStateException (); // TODO need error message here!
 getDelta().recordBundleRemovalComplete((BundleDescriptionImpl) bundle);
         removalPendings.remove(bundle);
     }

     private void resolveConstraints(BundleDescriptionImpl bundle, BundleDescription[] hosts, ExportPackageDescription[] selectedExports, BundleDescription[] resolvedRequires, ExportPackageDescription[] resolvedImports) {
         HostSpecificationImpl hostSpec = (HostSpecificationImpl) bundle.getHost();
         if (hostSpec != null) {
             if (hosts != null) {
                 hostSpec.setHosts(hosts);
                 for (int i = 0; i < hosts.length; i++)
                     ((BundleDescriptionImpl) hosts[i]).addDependency(bundle, true);
             }
         }

         bundle.setSelectedExports(selectedExports);
         bundle.setResolvedRequires(resolvedRequires);
         bundle.setResolvedImports(resolvedImports);

         bundle.addDependencies(hosts, true);
         bundle.addDependencies(resolvedRequires, true);
         bundle.addDependencies(resolvedImports, true);
         // add dependecies for generics
 GenericSpecification[] genericRequires = bundle.getGenericRequires();
         if (genericRequires.length > 0) {
             ArrayList genericSuppliers = new ArrayList(genericRequires.length);
             for (int i = 0; i < genericRequires.length; i++) {
                 GenericDescription[] suppliers = genericRequires[i].getSuppliers();
                 if (suppliers != null)
                     for (int j = 0; j < suppliers.length; j++)
                         genericSuppliers.add(suppliers[j]);
             }
             bundle.addDependencies((BaseDescription[]) genericSuppliers.toArray(new BaseDescription[genericSuppliers.size()]), true);
         }
     }

     private void unresolveConstraints(BundleDescriptionImpl bundle) {
         HostSpecificationImpl host = (HostSpecificationImpl) bundle.getHost();
         if (host != null)
             host.setHosts(null);

         bundle.setSelectedExports(null);
         bundle.setResolvedImports(null);
         bundle.setResolvedRequires(null);

         // remove suppliers for generics
 GenericSpecification[] genericRequires = bundle.getGenericRequires();
         if (genericRequires.length > 0)
             for (int i = 0; i < genericRequires.length; i++)
                 ((GenericSpecificationImpl) genericRequires[i]).setSupplers(null);
         bundle.removeDependencies();
     }

     private synchronized StateDelta resolve(boolean incremental, BundleDescription[] reResolve) {
         try {
             resolving = true;
             if (resolver == null)
                 throw new IllegalStateException ("no resolver set"); //$NON-NLS-1$
 fullyLoad();
             long start = 0;
             if (StateManager.DEBUG_PLATFORM_ADMIN_RESOLVER)
                 start = System.currentTimeMillis();
             if (!incremental) {
                 resolved = false;
                 reResolve = getBundles();
                 // need to get any removal pendings before flushing
 if (removalPendings.size() > 0) {
                     BundleDescription[] removed = getRemovalPendings();
                     reResolve = mergeBundles(reResolve, removed);
                 }
                 flush(reResolve);
             }
             if (resolved && reResolve == null)
                 return new StateDeltaImpl(this);
             if (removalPendings.size() > 0) {
                 BundleDescription[] removed = getRemovalPendings();
                 reResolve = mergeBundles(reResolve, removed);
             }
             // use the Headers class to handle ignoring case while matching keys (bug 180817)
 Headers[] tmpPlatformProperties = new Headers[platformProperties.length];
             for (int i = 0; i < platformProperties.length; i++) {
                 tmpPlatformProperties[i] = new Headers(platformProperties[i].size());
                 for (Enumeration keys = platformProperties[i].keys(); keys.hasMoreElements();) {
                     Object key = keys.nextElement();
                     tmpPlatformProperties[i].put(key, platformProperties[i].get(key));
                 }
             }
             resolver.resolve(reResolve, tmpPlatformProperties);
             resolved = removalPendings.size() == 0;

             StateDelta savedChanges = changes == null ? new StateDeltaImpl(this) : changes;
             changes = new StateDeltaImpl(this);

             if (StateManager.DEBUG_PLATFORM_ADMIN_RESOLVER) {
                 long time = System.currentTimeMillis() - start;
                 Debug.println("Time spent resolving: " + time); //$NON-NLS-1$
 cumulativeTime = cumulativeTime + time;
                 FrameworkDebugOptions.getDefault().setOption("org.eclipse.core.runtime.adaptor/resolver/timing/value", Long.toString(cumulativeTime)); //$NON-NLS-1$
 }
             if (savedChanges.getChanges().length > 0)
                 updateTimeStamp();
             return savedChanges;
         } finally {
             resolving = false;
         }
     }

     private BundleDescription[] mergeBundles(BundleDescription[] reResolve, BundleDescription[] removed) {
         if (reResolve == null)
             return removed; // just return all the removed bundles
 if (reResolve.length == 0)
             return reResolve; // if reResolve length==0 then we want to prevent pending removal
 // merge in all removal pending bundles that are not already in the list
 ArrayList result = new ArrayList(reResolve.length + removed.length);
         for (int i = 0; i < reResolve.length; i++)
             result.add(reResolve[i]);
         for (int i = 0; i < removed.length; i++) {
             boolean found = false;
             for (int j = 0; j < reResolve.length; j++) {
                 if (removed[i] == reResolve[j]) {
                     found = true;
                     break;
                 }
             }
             if (!found)
                 result.add(removed[i]);
         }
         return (BundleDescription[]) result.toArray(new BundleDescription[result.size()]);
     }

     private void flush(BundleDescription[] bundles) {
         resolver.flush();
         resolved = false;
         resolverErrors.clear();
         if (resolvedBundles.isEmpty())
             return;
         for (int i = 0; i < bundles.length; i++) {
             resolveBundle(bundles[i], false, null, null, null, null);
         }
         resolvedBundles.clear();
     }

     public StateDelta resolve() {
         return resolve(true, null);
     }

     public StateDelta resolve(boolean incremental) {
         return resolve(incremental, null);
     }

     public StateDelta resolve(BundleDescription[] reResolve) {
         return resolve(true, reResolve);
     }

     public void setOverrides(Object value) {
         throw new UnsupportedOperationException ();
     }

     public BundleDescription[] getResolvedBundles() {
         return (BundleDescription[]) resolvedBundles.elements(new BundleDescription[resolvedBundles.size()]);
     }

     public boolean isEmpty() {
         return bundleDescriptions.isEmpty();
     }

     void setResolved(boolean resolved) {
         this.resolved = resolved;
     }

     boolean basicAddBundle(BundleDescription description) {
         ((BundleDescriptionImpl) description).setContainingState(this);
         ((BundleDescriptionImpl) description).setStateBit(BundleDescriptionImpl.REMOVAL_PENDING, false);
         if (bundleDescriptions.add((BundleDescriptionImpl) description)) {
             if (description.getBundleId() > getHighestBundleId())
                 highestBundleId = description.getBundleId();
             return true;
         }
         return false;
     }

     void addResolvedBundle(BundleDescriptionImpl resolvedBundle) {
         resolvedBundles.add(resolvedBundle);
     }

     public ExportPackageDescription[] getExportedPackages() {
         fullyLoad();
         final List allExportedPackages = new ArrayList();
         for (Iterator iter = resolvedBundles.iterator(); iter.hasNext();) {
             BundleDescription bundle = (BundleDescription) iter.next();
             ExportPackageDescription[] bundlePackages = bundle.getSelectedExports();
             if (bundlePackages == null)
                 continue;
             for (int i = 0; i < bundlePackages.length; i++)
                 allExportedPackages.add(bundlePackages[i]);
         }
         for (Iterator iter = removalPendings.iterator(); iter.hasNext();) {
             BundleDescription bundle = (BundleDescription) iter.next();
             ExportPackageDescription[] bundlePackages = bundle.getSelectedExports();
             if (bundlePackages == null)
                 continue;
             for (int i = 0; i < bundlePackages.length; i++)
                 allExportedPackages.add(bundlePackages[i]);
         }
         return (ExportPackageDescription[]) allExportedPackages.toArray(new ExportPackageDescription[allExportedPackages.size()]);
     }

     BundleDescription[] getFragments(final BundleDescription host) {
         final List fragments = new ArrayList();
         for (Iterator iter = bundleDescriptions.iterator(); iter.hasNext();) {
             BundleDescription bundle = (BundleDescription) iter.next();
             HostSpecification hostSpec = bundle.getHost();

             if (hostSpec != null) {
                 BundleDescription[] hosts = hostSpec.getHosts();
                 if (hosts != null)
                     for (int i = 0; i < hosts.length; i++)
                         if (hosts[i] == host) {
                             fragments.add(bundle);
                             break;
                         }
             }
         }
         return (BundleDescription[]) fragments.toArray(new BundleDescription[fragments.size()]);
     }

     public void setTimeStamp(long newTimeStamp) {
         timeStamp = newTimeStamp;
     }

     private void updateTimeStamp() {
         if (getTimeStamp() == Long.MAX_VALUE)
             setTimeStamp(0);
         setTimeStamp(getTimeStamp() + 1);
     }

     public StateObjectFactory getFactory() {
         return factory;
     }

     void setFactory(StateObjectFactory factory) {
         this.factory = factory;
     }

     public BundleDescription getBundleByLocation(String location) {
         for (Iterator i = bundleDescriptions.iterator(); i.hasNext();) {
             BundleDescription current = (BundleDescription) i.next();
             if (location.equals(current.getLocation()))
                 return current;
         }
         return null;
     }

     public Resolver getResolver() {
         return resolver;
     }

     public void setResolver(Resolver newResolver) {
         if (resolver == newResolver)
             return;
         if (resolver != null) {
             Resolver oldResolver = resolver;
             resolver = null;
             oldResolver.setState(null);
         }
         resolver = newResolver;
         if (resolver == null)
             return;
         resolver.setState(this);
     }

     public boolean setPlatformProperties(Dictionary platformProperties) {
         return setPlatformProperties(new Dictionary[] {platformProperties});
     }

     public boolean setPlatformProperties(Dictionary[] platformProperties) {
         return setPlatformProperties(platformProperties, true);
     }

     synchronized boolean setPlatformProperties(Dictionary[] platformProperties, boolean resetSystemExports) {
         if (platformProperties.length == 0)
             throw new IllegalArgumentException ();
         // copy the properties for our use internally
 Dictionary[] newPlatformProperties = new Dictionary[platformProperties.length];
         for (int i = 0; i < platformProperties.length; i++) {
             newPlatformProperties[i] = new Hashtable(platformProperties[i].size());
             synchronized (platformProperties[i]) {
                 for (Enumeration keys = platformProperties[i].keys(); keys.hasMoreElements();) {
                     Object key = keys.nextElement();
                     newPlatformProperties[i].put(key, platformProperties[i].get(key));
                 }
             }
         }
         boolean result = false;
         if (this.platformProperties.length != newPlatformProperties.length) {
             result = true;
         } else {
             // we need to see if any of the existing filter prop keys have changed
 String [] keys = getPlatformPropertyKeys();
             for (int i = 0; i < newPlatformProperties.length && !result; i++)
                 result |= changedProps(this.platformProperties[i], newPlatformProperties[i], keys);
         }
         // always do a complete replacement of the properties in case new bundles are added that uses new filter props
 this.platformProperties = newPlatformProperties;
         if (resetSystemExports && result)
             resetSystemExports();
         return result;
     }

     private void resetSystemExports() {
         BundleDescription[] systemBundles = getBundles(Constants.getInternalSymbolicName());
         if (systemBundles.length > 0) {
             BundleDescriptionImpl systemBundle = (BundleDescriptionImpl) systemBundles[0];
             ExportPackageDescription[] exports = systemBundle.getExportPackages();
             ArrayList newExports = new ArrayList(exports.length);
             for (int i = 0; i < exports.length; i++)
                 if (((Integer ) exports[i].getDirective(ExportPackageDescriptionImpl.EQUINOX_EE)).intValue() < 0)
                     newExports.add(exports[i]);
             addSystemExports(newExports);
             systemBundle.setExportPackages((ExportPackageDescription[]) newExports.toArray(new ExportPackageDescription[newExports.size()]));
         }
     }

     private void addSystemExports(ArrayList exports) {
         for (int i = 0; i < platformProperties.length; i++)
             try {
                 ManifestElement[] elements = ManifestElement.parseHeader(Constants.EXPORT_PACKAGE, (String ) platformProperties[i].get(Constants.OSGI_FRAMEWORK_SYSTEM_PACKAGES));
                 if (elements == null)
                     continue;
                 // we can pass false for strict mode here because we never want to mark the system exports as internal.
 ExportPackageDescription[] systemExports = StateBuilder.createExportPackages(elements, null, null, null, 2, false);
                 Integer profInx = new Integer (i);
                 for (int j = 0; j < systemExports.length; j++) {
                     ((ExportPackageDescriptionImpl) systemExports[j]).setDirective(ExportPackageDescriptionImpl.EQUINOX_EE, profInx);
                     exports.add(systemExports[j]);
                 }
             } catch (BundleException e) {
                 // TODO consider throwing this...
 }
     }

     public Dictionary[] getPlatformProperties() {
         return platformProperties;
     }

     private boolean checkProp(Object origObj, Object newObj) {
         if ((origObj == null && newObj != null) || (origObj != null && newObj == null))
             return true;
         if (origObj == null)
             return false;
         if (origObj.getClass() != newObj.getClass())
             return true;
         if (origObj instanceof String )
             return !origObj.equals(newObj);
         String [] origProps = (String []) origObj;
         String [] newProps = (String []) newObj;
         if (origProps.length != newProps.length)
             return true;
         for (int i = 0; i < origProps.length; i++) {
             if (!origProps[i].equals(newProps[i]))
                 return true;
         }
         return false;
     }

     private boolean changedProps(Dictionary origProps, Dictionary newProps, String [] keys) {
         for (int i = 0; i < keys.length; i++) {
             Object origProp = origProps.get(keys[i]);
             Object newProp = newProps.get(keys[i]);
             if (checkProp(origProp, newProp))
                 return true;
         }
         return false;
     }

     public BundleDescription[] getRemovalPendings() {
         return (BundleDescription[]) removalPendings.toArray(new BundleDescription[removalPendings.size()]);
     }

     public synchronized ExportPackageDescription linkDynamicImport(BundleDescription importingBundle, String requestedPackage) {
         if (resolver == null)
             throw new IllegalStateException ("no resolver set"); //$NON-NLS-1$
 BundleDescriptionImpl importer = (BundleDescriptionImpl) importingBundle;
         if (importer.getDynamicStamp(requestedPackage) == getTimeStamp())
             return null;
         try {
             resolving = true;
             fullyLoad();
             // ask the resolver to resolve our dynamic import
 ExportPackageDescriptionImpl result = (ExportPackageDescriptionImpl) resolver.resolveDynamicImport(importingBundle, requestedPackage);
             if (result == null)
                 importer.setDynamicStamp(requestedPackage, new Long (getTimeStamp()));
             else {
                 importer.setDynamicStamp(requestedPackage, null); // remove any cached timestamp
 // need to add the result to the list of resolved imports
 importer.addDynamicResolvedImport(result);
             }
             setDynamicCacheChanged(true);
             return result;
         } finally {
             resolving = false;
         }
     }

     void setReader(StateReader reader) {
         this.reader = reader;
     }

     StateReader getReader() {
         return reader;
     }

     public void fullyLoad() {
         if (reader == null)
             return;
         synchronized (reader) {
             if (fullyLoaded == true)
                 return;
             if (reader.isLazyLoaded())
                 reader.fullyLoad();
             fullyLoaded = true;
         }
     }

     public void unloadLazyData(long expireTime) {
         // make sure no other thread is trying to unload or load
 synchronized (reader) {
             if (reader.getAccessedFlag()) {
                 reader.setAccessedFlag(false); // reset accessed flag
 return;
             }
             fullyLoaded = false;
             BundleDescription[] bundles = getBundles();
             for (int i = 0; i < bundles.length; i++)
                 ((BundleDescriptionImpl) bundles[i]).unload();
         }
     }

     public ExportPackageDescription[] getSystemPackages() {
         ArrayList result = new ArrayList();
         BundleDescription[] systemBundles = getBundles(Constants.getInternalSymbolicName());
         if (systemBundles.length > 0) {
             BundleDescriptionImpl systemBundle = (BundleDescriptionImpl) systemBundles[0];
             ExportPackageDescription[] exports = systemBundle.getExportPackages();
             for (int i = 0; i < exports.length; i++)
                 if (((Integer ) exports[i].getDirective(ExportPackageDescriptionImpl.EQUINOX_EE)).intValue() >= 0)
                     result.add(exports[i]);
         }
         return (ExportPackageDescription[]) result.toArray(new ExportPackageDescription[result.size()]);
     }

     boolean inStrictMode() {
         return Constants.STRICT_MODE.equals(getPlatformProperties()[0].get(Constants.OSGI_RESOLVER_MODE));
     }

     public synchronized ResolverError[] getResolverErrors(BundleDescription bundle) {
         if (bundle.isResolved())
             return new ResolverError[0];
         ArrayList result = (ArrayList) resolverErrors.get(bundle);
         return result == null ? new ResolverError[0] : (ResolverError[]) result.toArray(new ResolverError[result.size()]);
     }

     public synchronized void addResolverError(BundleDescription bundle, int type, String data, VersionConstraint unsatisfied) {
         if (!resolving)
             throw new IllegalStateException (); // TODO need error message here!
 ArrayList errors = (ArrayList) resolverErrors.get(bundle);
         if (errors == null) {
             errors = new ArrayList(1);
             resolverErrors.put(bundle, errors);
         }
         errors.add(new ResolverErrorImpl((BundleDescriptionImpl) bundle, type, data, unsatisfied));
     }

     public synchronized void removeResolverErrors(BundleDescription bundle) {
         if (!resolving)
             throw new IllegalStateException (); // TODO need error message here!
 resolverErrors.remove(bundle);
     }

     public boolean dynamicCacheChanged() {
         return dynamicCacheChanged;
     }

     void setDynamicCacheChanged(boolean dynamicCacheChanged) {
         this.dynamicCacheChanged = dynamicCacheChanged;
     }

     public StateHelper getStateHelper() {
         return StateHelperImpl.getInstance();
     }

     void addPlatformPropertyKeys(String [] keys) {
         synchronized (platformPropertyKeys) {
             for (int i = 0; i < keys.length; i++)
                 if (!platformPropertyKeys.contains(keys[i]))
                     platformPropertyKeys.add(keys[i]);
         }
     }

     String [] getPlatformPropertyKeys() {
         synchronized (platformPropertyKeys) {
             return (String []) platformPropertyKeys.toArray(new String [platformPropertyKeys.size()]);
         }
     }

     public long getHighestBundleId() {
         return highestBundleId;
     }

 }

